<?php
/**
 * 订单处理公共函数
 * Created by PhpStorm.
 * Author: zzx
 * Date: 2017/6/21
 * Time: 15:55
 */


/**
 * CURL
 * @param $url 请求地址
 * @param string $type 请求类型，GET或POST
 * @param string $data 请求内容
 * @param $Async 异步超时毫秒
 * @return mixed|string
 */
function curl($url, $type = 'GET', $data = '', $Async = 500)
{
    //$Asynchronous：异步请求
    $ch = curl_init();
    if ($Async) {
        curl_setopt($ch, CURLOPT_NOSIGNAL, true);//注意，毫秒超时一定要设置这个
        curl_setopt($ch, CURLOPT_TIMEOUT_MS, $Async);//超时毫秒，小于500时不稳定，测试在600以上可以
    }
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)');
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $type);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $info = curl_exec($ch);
    if (curl_errno($ch)) {
        return 'Errno' . curl_error($ch);
    }
    curl_close($ch);
    return $info;
}


/**
 * 文件写入
 * @access public
 * @param string $filename 文件名
 * @param string $content 文件内容
 * @return boolean
 */
function put($filename, $content, $type = '')
{
    $dir = dirname($filename);
    if (!is_dir($dir)) {
        mkdir($dir, 0777, true);
    }
    if (false === file_put_contents($filename, $content)) {
        // 写入错误日志文件
    }
}


/**
 * 错误日志记录
 */
function order_error_log($string)
{
    $string = '时间:' . date('Y-m-d H:i:s') . $string . PHP_EOL;
    file_put_contents(dirname(__FILE__) . '/error.log', $string, FILE_APPEND);
}


/**
 * 订单数据库操作错误日志记录
 * @param string $string 错误描述
 * @param string $sql 执行SQL语句
 * @param object $my_conn MYSQL实例
 */
function mysql_error_log($string, $sql, $my_conn)
{
    $file = dirname(__FILE__) . '/mysql_error_log/';
    if (!is_dir($file)) {
        mkdir($file, 0777, true);
    }
    // 获取mysql错误代码
    $mysqli_errno = mysqli_errno($my_conn);

    // 获取mysql错误描述
    $mysqli_error = mysqli_error($my_conn);

    $string = date('H-m-d H:i:s') . $string . ', 执行SQL:' . $sql . ', MYSQL错误代码:' . $mysqli_errno . ', MYSQL错误描述:' . $mysqli_error;
    file_put_contents($file . date('YmdH') . '.log', $string . PHP_EOL, FILE_APPEND);
}


/**
 * 错误订单重复尝试
 * @param $re 订单原始数据
 * @param $err_orders 错误订单表
 * @param $time 平仓时间
 * @param $sellprice 平仓点位
 * @param $step 当前执行到哪一步
 * @return boolean 返回值 重试次数已经超过3次 return true or false
 */
function err_rep($re, $err_orders, $time, $sellprice, $step, $table_key)
{
    $err_orders->incr($table_key, 'err_num'); // 重复尝试次数
    // 检查重复次数
    $edata = $err_orders->get($table_key); // 记录重复次数
    if ($edata['err_num'] >= 3) {
        // 保存当前记录和状态到日志文件，不再尝试。
        $string = '操作时间：' . date('Y-m-d H:i:s', $time) . ', 操作步骤：' . $step . ', 操作信息：';
        foreach ($re as $key => $value) {
            # code...
            $string .= $key . ':' . $value . ',';
        }
        $file = dirname(__FILE__) . '/error_log/';
        if (!is_dir($file)) {
            mkdir($file, 0777, true);
        }
        file_put_contents($file . date('Y-m-d') . 'error.log', $string . PHP_EOL, FILE_APPEND);
        return true; //重试次数已经超过3次
    }

    // 保存当前数据
    $edata = array(
        'time' => $time, //平仓时间
        'sellprice' => $sellprice // 平仓点位
    );
    $err_orders->set($table_key, $edata); // 保存当前数据

    return false;
}


/**
 * 交易时间判断
 * @param $time
 * @return array
 */
function check_time($time)
{
    global $gpstr;

    $holiday = $gpstr->get('holiday')['val']; // 法定假日
    $start_time1 = $gpstr->get('start_time1')['val']; // 上午开盘时间
    $end_time1 = $gpstr->get('end_time1')['val']; // 上午停盘时间
    $start_time2 = $gpstr->get('start_time2')['val']; // 下午开盘时间
    $end_time2 = $gpstr->get('end_time2')['val']; // 下午停盘时间
    $sat_closed = $gpstr->get('sat_closed')['val']; // 周六休市
    $sun_closed = $gpstr->get('sun_closed')['val']; // 周日休市

    // 转换
    $start_time1 = strtotime(date('Y-m-d ') . $start_time1);
    $end_time1 = strtotime(date('Y-m-d ') . $end_time1);
    $start_time2 = strtotime(date('Y-m-d ') . $start_time2);
    $end_time2 = strtotime(date('Y-m-d ') . $end_time2);

    // 法定假日
    if (in_array(date('Y-n-j', $time), explode(',', $holiday))) {
        return ['status' => 0, 'info' => '今天休市。'];
    }

    // 周六休市
    if (date('w', $time) == 6 && $sat_closed) {
        return ['status' => 0, 'info' => '周六休市。'];
    }

    // 周日休市
    if (date('w', $time) == 0 && $sun_closed) {
        return ['status' => 0, 'info' => '周日休市。'];
    }

    // 交易时间
    if ($time < $start_time1 || $time > $end_time1 && $time < $start_time2 || $time > $end_time2) {
        return ['status' => 0, 'info' => '非交易时间。'];
    }
}


/**
 * 消息通知客户端
 * @param array $data 推送信息，包括：消息内容，客户端连接标识，[用户最新余额]
 * @param integer $status 订单处理状态
 */
function send_message_to_client($data, $status = 0)
{
    global $gpstr;

    $url = $gpstr->get('data_source')['val'];
    $data['status'] = $status;
    curl("http://{$url}", 'POST', 'type=send_message_to_client&data=' . json_encode($data));
}


/**
 * 获取当期彩票信息
 * ssc_openeriod 时时彩上期开奖期号
 * ssc_winningNumber 时时彩上期开奖结果
 * ssc 时时彩本期下单期号
 * ssc_openTime 时时彩本期即将开奖时间
 * kl8_Issue 快乐8上期开奖期号
 * kl8_WinNumber 快乐8上期开奖结果
 * kl8 快乐8本期下单期号
 * kl8_openTime 快乐8本期即将开奖时间
 */
function get_current()
{
    global $redis, $config, $current, $gpstr; // web端参数，行情数据表
    $current = $redis->hGetAll($config['REDIS_PREFIX'] . 'datas_' . 'current'); // 当期彩票信息
    
    $gpstr->set('current', ['val' => json_encode($current)]);
    
    if ($current['ssc'] && $current['kl8']) {
        return true;
    }
}

/**
 * 清空已经开奖的数据，防止重复评仓处理
 */
function clear_current($data)
{
    global $redis, $config, $current, $gpstr; // web端参数，行情数据表
    
    if($data['clear_type'] == 'ssc'){
        $redis->hMset($config['REDIS_PREFIX'] . 'datas_' . 'current', [
            'ssc_openeriod'=>'',
            'ssc_winningNumber'=>'',
        ]);
    }
    if($data['clear_type'] == 'kl8'){
        $redis->hMset($config['REDIS_PREFIX'] . 'datas_' . 'current', [
            'kl8_Issue'=>'',
            'kl8_WinNumber'=>'',
        ]);
    }
    
    $current = $redis->hGetAll($config['REDIS_PREFIX'] . 'datas_' . 'current'); // 当期彩票信息
    
    $gpstr->set('current', ['val' => json_encode($current)]);
}

/**
 * 将开奖结果写入数据库
 * @param arr $data
 */
function check_winresult($data = [])
{
    global $redis, $config, $current, $gpstr, $my_conn; // web端参数，行情数据表
    
    if($redis->lSize($config['REDIS_PREFIX'] . 'datas_win_queue') >= 1){
        $result = $redis->rPop($config['REDIS_PREFIX'] . 'datas_win_queue');
        
        $result = json_decode($result, true);
        
        try {
            $r_sql = "insert into zzx_win (period, win_numbers) value ('{$result[0]}', '{$result[1]}')";
            mysqli_query($my_conn, $r_sql);
        } catch (Exception $e){
            // do nothing
        }
    }
}


/**
 * 同步机构流水表，客户流水表，历史订单表，到各自的统计表
 */
function sysnc()
{
    global $my_conn;
    $d_time = strtotime('-1 day');

    // 删除统计表当天的数据
    $sql_jg = "delete from zzx_balanceinfo_jg_sam where createtime >={$d_time}"; // 机构流水统计表
    $sql_kh = "delete from zzx_balanceinfo_kh_sam where createtime >={$d_time}"; // 客户流水统计表
    $sql_o = "delete from zzx_order_history_sam where selltime >={$d_time}"; // 历史订单统计表
    mysqli_query($my_conn, $sql_jg);
    mysqli_query($my_conn, $sql_kh);
    mysqli_query($my_conn, $sql_o);

    // 复制当天的数据到统计表
    $sql_jg = "insert zzx_balanceinfo_jg_sam select * from zzx_balanceinfo_jg  where createtime >={$d_time}"; // 机构流水统计表
    $sql_kh = "insert zzx_balanceinfo_kh_sam select * from zzx_balanceinfo_kh where createtime >={$d_time}"; // 客户流水统计表
    $sql_o = "insert zzx_order_history_sam select * from zzx_order_history where selltime >={$d_time}"; // 历史订单统计表
    mysqli_query($my_conn, $sql_jg);
    mysqli_query($my_conn, $sql_kh);
    mysqli_query($my_conn, $sql_o);
}


/**
 * 清除用户持仓信息
 * @param $uid 用户uid
 */
function clear_uorder($uid = null)
{
    global $uinfo;
    if (!$uinfo) {
        return;
    }
    if ($uid) {
        $uinfo->del($uid);
    } else {
        foreach ($uinfo as $key => $row) {
            $uinfo->del($key);
        }
    }
}


/**
 * 并发控制
 * @param string $type 控制类型
 * @param null $data 数据
 * @param $redis redis连接实例
 * @return bool
 */
function concurrent_ctrl($type = '', $data = null, $redis = null)
{

    global $table, $q_table, $gp, $config, $server;

    switch ($type) {
        case 'insert':
            // 下单控制
            $queue = count($server->add_buffer); // 当前下单队列数量
            $max_buy = $gp->get('max_buy')['val']; // 并发控制，每秒最大下单数量
            // 下单队列超过阀值，或系统繁忙，不让下单
            if ($queue >= $max_buy || $gp->get('is_busy')['val']) {
                return true;
            }
            break;

        case 'upspeed':
            // 更新下单和平仓处理速度，计算最大下单速度，设定最大持仓数量
            $s = round(count($data['odata']) / ($data['t2'] - $data['t1']));
            // 更新redis
            $data['r_type'] = 'hSet';
            $data['r_data']['key'] = $config['REDIS_PREFIX'] . 's_concurrent';
            $data['r_data']['hkey'] = $data['hkey'];
            $data['r_data']['val'] = $s;
            $f_data['type'] = 'up_redis';
            $f_data['data'] = $data;
            $server->finish($f_data);
            // 控制
            $max_otime = $gp->get('max_otime')['val']; // 最大订单时间
            $gp->set('max_buy', array('val' => $s)); // 设定每秒每个进程最大下单数量 = 处理速度
            $gp->set('max_queue', array('val' => $s * $max_otime)); // 设定最大持仓队列数量 = 处理速度 * 最大订单时间
            // 调试开关
            $is_debug = $gp->get('is_debug')['val'];
            if ($is_debug == 1 | $is_debug == 2 | $is_debug == 8) {
                echo "处理速度： {$data['hkey']} $s|";
                if ($is_debug == 2) {
                    order_error_log(" 处理速度： {$data['hkey']} $s|");
                }
            }
            break;

        case 'add_buffer': // 每秒执行1次，运行在每个worker中

            // 当前下单队列数量
            $queue = count($server->add_buffer);
            if ($queue) {
                $gp->incr('add_queue', 'val', $queue); // 累计每个worker
            }
            // 调试
            $is_debug = $gp->get('is_debug')['val'];
            if ($is_debug == 1 | $is_debug == 2 | $is_debug == 3) {
                echo "下单：$queue wk_id：$server->worker_id|";
                if ($is_debug == 2) {
                    order_error_log(" 下单：$queue wk_id：$server->worker_id|");
                }
            }
            break;

        default: // 每秒执行1次，运行在worker1中

            # 排队任务数量 ################# 放此位置只统计下单与平仓的，放后面执行会包括更新redis的任务
            $tasking_num = $server->stats()['tasking_num'];
            // 更新redis
            $data['r_type'] = 'hSet';
            $data['r_data']['key'] = $config['REDIS_PREFIX'] . 's_concurrent';
            $data['r_data']['hkey'] = 'tasking_num';
            $data['r_data']['val'] = $tasking_num;
            $server->task(array('type' => 'up_redis', 'data' => $data), 1);
            // 调试
            $is_debug = $gp->get('is_debug')['val'];
            if ($is_debug == 1 | $is_debug == 2 | $is_debug == 6) {
                echo "排队任务：$tasking_num|";
                if ($is_debug == 2) {
                    order_error_log(" 排队任务：$tasking_num|");
                }
            }

            # 下单队列数量 ###################
            $queue = $gp->get('add_queue')['val']; // 读取计数
            // 更新redis
            $data['r_type'] = 'hSet';
            $data['r_data']['key'] = $config['REDIS_PREFIX'] . 's_concurrent';
            $data['r_data']['hkey'] = 'add_queue';
            $data['r_data']['val'] = $queue;
            $server->task(array('type' => 'up_redis', 'data' => $data), 1);
            // 清零计数
            $gp->set('add_queue',['val' => 0]);
            // 调试
            $is_debug = $gp->get('is_debug')['val'];
            if ($is_debug == 1 | $is_debug == 2 | $is_debug == 3) {
                echo "下单缓存：$queue|";
                if ($is_debug == 2) {
                    order_error_log(" 下单缓存：$queue|");
                }
            }

            # 当前持仓队列数量 #################
            $queue = count($table);
            // 更新redis
            $data['r_type'] = 'hSet';
            $data['r_data']['key'] = $config['REDIS_PREFIX'] . 's_concurrent';
            $data['r_data']['hkey'] = 'position_queue';
            $data['r_data']['val'] = $queue;
            $server->task(array('type' => 'up_redis', 'data' => $data), 1);
            // 控制，当持仓数量 >= 设定的最大持仓数量时，不处理下单缓存中的订单
            if ($queue) {
                $max_queue = $gp->get('max_queue')['val']; // 并发控制，最大持仓队列数量
                if ($queue >= $max_queue) {
                    $gp->set('is_add', array('val' => 0)); // 不处理下单队列
                } else {
                    $gp->set('is_add', array('val' => 1)); // 处理下单队列
                }
            } else {
                $gp->set('is_add', array('val' => 1)); // 处理下单队列
            }
            // 调试
            $is_debug = $gp->get('is_debug')['val'];
            if ($is_debug == 1 | $is_debug == 2 | $is_debug == 4) {
                echo "持仓队列：$queue|";
                if ($is_debug == 2) {
                    order_error_log(" 持仓队列：$queue|");
                }
            }

            # 平仓中数量 #################
            $closeing = count($q_table);
            // 更新redis
            $data['r_type'] = 'hSet';
            $data['r_data']['key'] = $config['REDIS_PREFIX'] . 's_concurrent';
            $data['r_data']['hkey'] = 'closeing';
            $data['r_data']['val'] = $closeing;
            $server->task(array('type' => 'up_redis', 'data' => $data), 1);
            // 调试
            $is_debug = $gp->get('is_debug')['val'];
            if ($is_debug == 1 | $is_debug == 2 | $is_debug == 5) {
                echo "平仓中：$closeing|";
                if ($is_debug == 2) {
                    order_error_log(" 平仓中：$closeing|");
                }
            }

            # 超时订单数量 #####################
            $time = time();
            $num = 0;
            if (count($table)) {
                $sell_overtime = $gp->get('sell_overtime')['val']; // 并发控制，允许平仓超时多少秒
                foreach ($table as $key => $row) {
                    if ($time - $row['selltime'] > $sell_overtime) {
                        $num += 1;
                    }
                }
            }
            // 控制，当有超时订单时，设置系统为繁忙，不让下单
            if ($num) {
                $gp->set('is_busy', array('val' => 1)); // 设置系统为繁忙
            } else {
                $gp->set('is_busy', array('val' => 0)); // 清除繁忙
            }
            // 更新redis
            $data['r_type'] = 'hSet';
            $data['r_data']['key'] = $config['REDIS_PREFIX'] . 's_concurrent';
            $data['r_data']['hkey'] = 'c_overtime_num';
            $data['r_data']['val'] = $num;
            $server->task(array('type' => 'up_redis', 'data' => $data), 1);
            // 调试
            $is_debug = $gp->get('is_debug')['val'];
            if ($is_debug == 1 | $is_debug == 2 | $is_debug == 7) {
                echo "超时：$num|";
                if ($is_debug == 2) {
                    order_error_log(" 超时：$num|");
                }
            }
            if ($is_debug) { // 换行
                echo "\n";
            }

            # 日志文件 ###############################
            if ($gp->get('is_debug')['val'] == 2) {
                foreach ($table as $k => $v) {
                    var_dump($v);
                    order_error_log(json_encode($v));
                }
                $gp->set('is_debug',['val' => 0]);
                // 更新redis
                $data['r_type'] = 'hSet';
                $data['r_data']['key'] = $config['REDIS_PREFIX'] . 'conf_s_supervene';
                $data['r_data']['hkey'] = 'is_debug';
                $data['r_data']['val'] = 0;
                $server->task(array('type' => 'up_redis', 'data' => $data), 1);
            }
            break;
    }
}


/**
 * 更新redis
 * @param $data 更新的key和val
 * @param string $type 更新类型
 */
function up_redis($data, $type = 'hSet') {

    global $redis;

    $key = $data['key'];
    $hkey = $data['hkey'];
    $val = $data['val'];

    switch ($type) {

        case 'hSet':
            $redis->hSet($key, $hkey, $val);
            break;

        default:
            break;
    }
}
